#pragma once

#include "../utils/utils.hpp"
#include <vector>
#include <string>

// The omelette domain is mainly based on the PDDL-ish
// description from Bonet and Geffner (2001):
// 	http://ldc.usb.ve/~bonet/reports/gpt-workshop.pdf

namespace omelette {

enum {
	// NBowls is the number of bowls in the omlet problem.
	// This must be ≥2.
	NBowls = 2,

	// NEggs is the number of eggs to use for the omelette.
	NEggs = 3,
};

// EggColor is either a white egg or a brown egg.
enum EggColor {
	White, Brown,
};

// HandState names the state of the hand.
// It is either empty, holding a bad egg, or
// it is holding a good egg.  If it is holding
// an egg, the agent doesn't know if it's good
// or bad yet; it must sense.
enum HandState {
	Empty, Bad, Good,
};

// BrownProb is the probability that an egg is brown.
extern double BrownProb;

// BadProb is the probability that an egg is bad, indexed
// by the EggColor.
extern double BadProb[2];

class Op {
public:

	// Kind is the type of operator.
	enum Kind {
		None = -1,

		// Grab grabs an egg if the hand is empty.
		// The a and b fields are not used.
		Grab,

		// BreakEgg breaks an egg into a bowl if the
		// bowl has fewer than NEggs eggs already
		// in it.  The a field is the bowl index into
		// which the egg should be broken, and the
		// b field is not used.
		BreakEgg,

		// Pour pours the contents of a bowl into
		// another bowl if source bowl is not empty
		// and the destination bowl will not contain
		// more than NEggs eggs after.  The a field
		// is the source bowl and the b field is the
		// destination field.
		Pour,

		// Clean cleans out a bowl, setting its number
		// of good and bad eggs to zero.  The a field is
		// the index of the bowl to clean, and the b field
		// is unused.
		Clean,

		// Smell smells a bowl to determine if it
		// contains any bad eggs.  The a field is
		// the index of the bowl to smell, and the
		// b field is usnused.
		Smell,
	};

	Op() {
	}

	// Grab returns a grab operator.
	static Op grab() {
		return Op(Grab, 0, 0);
	}

	// BreakEgg returns a break operator for the given bowl.
	static Op breakegg(int b) {
		return Op(BreakEgg, b, 0);
	}

	// Pour returns a pour operator for the given source
	// and destination bowls.
	static Op pour(int src, int dst) {
		return Op(Pour, src, dst);
	}

	// Clean returns a clean operator for the given bowl.
	static Op clean(int b) {
		return Op(Clean, b, 0);
	}

	// Smell returns a smell operator for the given bowl.
	static Op smell(int b) {
		return Op(Smell, b, 0);
	}

	// Nop is an invalid operator that the search can
	// use as a placeholder.
	static const Op Nop;

	bool operator==(const Op &o) const {
		return kind == o.kind && a == o.a && b == o.b;
	}

	bool operator!=(const Op &o) const {
		return !(*this == o);
	}

	// Str returns the string representation of the operator.
	std::string str() const;

	// Kind is the operator kind.
	Kind kind;

	// A and B are used as arguments to some of the
	// operators as described in the comments for
	// the Kind enum.
	int a, b;

private:

	// This is private so that operators must be constructed
	// via the static member to guarantee that the unused
	// fields are zeroed.  Forcing unused fields to zero makes
	// comparison simpler.
	Op(Kind k, int x, int y) : kind(k), a(x), b(y) {
	}
};

// Bowl holds information about a single bowl into
// which eggs can be poured.
class Bowl {
public:

	Bowl() :
		known(true), bad(false), n(0), nbrown(0) {
	}

	Bowl(const Bowl &o) :
		known(o.known), bad(o.bad), n(o.n), nbrown(o.nbrown) {
	}

	bool operator==(const Bowl &o) const {
		return known == o.known && bad == o.bad &&
			n == o.n && nbrown == o.nbrown;
	}

	bool operator!=(const Bowl &o) const {
		return !(*this == o);
	}

	// Known is true if the agent knows that the
	// bowl contains no bad eggs.
	bool known;

	// Bad is true if this bowl contains bad eggs.
	bool bad;

	// N is the number of eggs in this bowl.
	int n;

	// Nbrown is the number of eggs in this bowl
	// that are brown.
	int nbrown;
};

// State is the state of the omelette world.
class State {
public:

	State() : hand(Empty), next(White), rng(0) {
	}

	State(const Rand &r) :
		hand(Empty), handcolor(White), rng(r) {
		next = rng.real() < BrownProb ? Brown : White;
	}

	State(const State &o) :
		hand(o.hand), handcolor(o.handcolor), next(o.next), rng(o.rng) {
		for (int i = 0; i < NBowls; i++)
			bowls[i] = o.bowls[i];
	}

	bool operator==(const State &o) const;

	bool operator!=(const State &o) const {
		return !(*this == o);
	}

	// Isgoal returns true if this is the goal state.
	bool isgoal() const;

	// Ops returns the applicable operators in this state.
	std::vector<Op> ops() const;

	// Act returns a new state that is the result of
	// performing an operation in the target state.
	State act(const Op&) const;

	// Future returns a new state with a, possibly
	// different, hallucinated future.
	State future() const;

	// Hash returns the hash value of the state.
	unsigned long hash() const;

	// Str returns the human-readable string representation
	// of this state.
	std::string str() const;

	// Bowls is the state of each bowl, the 0th
	// bowl is the pan.
	Bowl bowls[NBowls];

	// Hand is the state of the hand.
	HandState hand;

	// Handcolor is the color of the egg in the hand.
	EggColor handcolor;

	// Next is the color of the next egg.
	EggColor next;

	// Rng is the random number generator state
	// used to select the goodness of the next egg.
	Rand rng;
};

// Solve optimally solves a determinized state.
std::vector<Op> solve(const State&);

};	// namespace omelette